package org.particles.gui;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.util.ArrayList;
import java.util.List;

import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.ListSelectionModel;
import javax.swing.border.Border;
import javax.swing.event.ListSelectionListener;

import org.particles.domain.Statistic;
import org.particles.domain.sim.SimParam;
import org.particles.domain.sim.SimulationCase;
import org.particles.gui.statistic.StatisticPanel;

/**
 * should hold simulation list together with
 * parameters and statistic.
 */
public class SimCtrlPanel extends JPanel {
	public static final int FIELDSTATUS_OK = 1;
	public static final int FIELDSTATUS_ERROR = 2;
	private static final long serialVersionUID = 3323234889222277190L;
	/** layout for control panel */
	private final GridBagLayout layout = new GridBagLayout();
	private JList simList;
	/** fields in which user enters simulation parameters. */
	private List<JTextField> textFields;
	/** 
	 * simulation parameters that will be available after calling 
	 * inputErrorsAvailable method. 
	 */
	private List<Double> simParams;
	/** border for fields that contains no errors */
	private Border okBorder;
	/** border for fields that contains errors */
	private final Border errorBorder = BorderFactory.createLineBorder(Color.RED, 1);
	private final JButton applyButton = new JButton("Apply");
	private SimulationCase[] simulations;
	private List<StatisticPanel> statisticPanels = new ArrayList<StatisticPanel>(5);
	private Action updateStatisticAction;
	
	/**
	 * @param simulations must contain at least one simulation.
	 * @throws IndexOutOfBoundsException if size of simulations is 0.
	 */
	public SimCtrlPanel(final SimulationCase[] simulations) {
		this.simulations = simulations;
		setLayout(layout);
		setBorder(BorderFactory.createEmptyBorder(5, 1, 2, 1));
		
		// add list
		simList = new JList(this.simulations);
		simList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
		simList.setLayoutOrientation(JList.VERTICAL);
		simList.setSelectedIndex(0);
		recreatePanel(simulations[0]);
	}
	
	public void recreatePanel(final SimulationCase simulation) {
		removeAll();
		
		// add list
		add(simList, createCtrlPanelCnstr(GridBagConstraints.WEST));
		
		// add parameter panel
		final JPanel paramsPanel = createParameterPanel(simulation);
		add(paramsPanel, createCtrlPanelCnstr(GridBagConstraints.WEST));
		
		// add apply button
		GridBagConstraints cnstr = createCtrlPanelCnstr(GridBagConstraints.NORTH);
		cnstr.weighty = 1;
		add(this.applyButton, cnstr);
		
		// add statistic panel
		statisticPanels.clear();
		for(final Statistic statistic : simulation.getStatistics()) {
			StatisticPanel statisticPanel = new StatisticPanel(statistic);
			statisticPanel.setUpdateStatAction(this.updateStatisticAction);
			statisticPanel.repaintStatistic();
			cnstr = createCtrlPanelCnstr(GridBagConstraints.WEST);
			cnstr.weightx = 1;
			cnstr.fill = GridBagConstraints.HORIZONTAL;
			add(statisticPanel, cnstr);
			statisticPanels.add(statisticPanel);
		}
		
		revalidate();
	}
	
	private GridBagConstraints createCtrlPanelCnstr(final int anchor) {
		GridBagConstraints layoutConstraints = new GridBagConstraints();
		layoutConstraints.gridx = 0;
		layoutConstraints.gridwidth = GridBagConstraints.REMAINDER;
		layoutConstraints.anchor = anchor;
		layoutConstraints.insets = new Insets(2, 0, 2, 0);
		return layoutConstraints;
	}
	
	private JPanel createParameterPanel(final SimulationCase sim) {
		final List<SimParam> params = sim.availableParams();
		this.textFields = new ArrayList<JTextField>(params.size());
		final JPanel paramsPanel = new JPanel();
		final GridBagLayout paramsPanelLayout = new GridBagLayout();
		paramsPanel.setLayout(paramsPanelLayout);
		//paramsPanel.setPreferredSize(new Dimension(180, 30));
		for(final SimParam param : params) {
			// add text fields
			final JTextField textField = new JTextField(String.valueOf(param.getValue()));
			textFields.add(textField);
			textField.setPreferredSize(new Dimension(50, 20));
			if(param.getDesc() != null) {
				textField.setToolTipText(param.getDesc());
			}
			if(this.okBorder == null) {
				this.okBorder = textField.getBorder();
			}
			else {
				textField.setBorder(this.okBorder);
			}
			paramsPanelLayout.setConstraints(textField, createParamPanelCnstr(0));
			paramsPanel.add(textField);
			
			// add label
			final JLabel label = new JLabel(param.getName());
			label.setToolTipText(param.getName());
			paramsPanelLayout.setConstraints(label, createParamPanelCnstr(1));
			paramsPanel.add(label);
		}
		return paramsPanel;
	}
	
	private GridBagConstraints createParamPanelCnstr(final int gridx) {
		GridBagConstraints layoutConstraints = new GridBagConstraints();
		layoutConstraints.gridx = gridx;
		layoutConstraints.anchor = GridBagConstraints.WEST;
		layoutConstraints.insets = new Insets(1, 1, 1, 1);
		return layoutConstraints;
	}
	
	public void setUpdateStatAction(final Action action) {
		this.updateStatisticAction = action;
		for(StatisticPanel panel : getStatisticPanels()) {
			panel.setUpdateStatAction(action);
		}
	}
	
	public List<StatisticPanel> getStatisticPanels() {
		return statisticPanels;
	}
	
	public void repaintStatistics() {
		for(StatisticPanel panel : statisticPanels) {
			panel.repaintStatistic();
		}
	}

	/** 
	 * @return simulation parameters that will be available after calling 
	 * {@link #inputErrorsAvailable inputErrorsAvailable} method. 
	 */
	public List<Double> getSimParams() {
		return simParams;
	}

	/**
	 * @return current simulation selected in the list.
	 */
	public SimulationCase getCurrentSimulation() {
		return (SimulationCase) simList.getSelectedValue();
	}
	
	/**
	 * @param action action for apply button
	 */
	public void setApplyAction(final Action action) {
		this.applyButton.setAction(action);
	}
	
	/**
	 * set OK or error status on a field.
	 * @param fieldStatus value FIELDSTATUS_OK or FIELDSTATUS_ERROR.
	 */
	private void setFieldStatus(final JTextField field, final int fieldStatus) {
		if(FIELDSTATUS_OK == fieldStatus 
				&& this.okBorder != field.getBorder()) {
			field.setBorder(this.okBorder);
			field.setToolTipText(null);
		}
		else if(FIELDSTATUS_ERROR == fieldStatus 
				&& this.errorBorder != field.getBorder()) {
			field.setBorder(this.errorBorder);
		}
	}
	
	private void setFieldStatusError(final JTextField field, final String errorText) {
		field.setToolTipText(errorText);
		setFieldStatus(field, SimCtrlPanel.FIELDSTATUS_ERROR);
	}
	
	/**
	 * search for errors in user input. 
	 * @return true if errors are available.
	 */
	public boolean inputErrorsAvailable() {
		boolean errorsAvailable = false;
		this.simParams = new ArrayList<Double>(textFields.size());
		final List<SimParam> params = getCurrentSimulation().availableParams();
		for(int i = 0; i < textFields.size(); i++) {
			final JTextField textField = textFields.get(i);
			try {
				double userValue = Double.valueOf(textField.getText());
				final SimParam param = params.get(i);
				if(param.getMinValue() != null && userValue < param.getMinValue()
						|| param.getMaxValue() != null && userValue > param.getMaxValue()) {
					String errorText = null;
					if(param.getMinValue() != null && userValue < param.getMinValue()) {
						errorText = "value should be equal or greater than " + param.getMinValue();
					}
					if(param.getMaxValue() != null && userValue > param.getMaxValue()) {
						if(errorText != null) {
							errorText += " and equal or lesser than " + param.getMaxValue();
						}
						else {
							errorText = "value should be equal or lesser than " + param.getMaxValue();
						}
					}
					setFieldStatusError(textField, errorText);
					errorsAvailable = true;
				}
				else {
					setFieldStatus(textField, SimCtrlPanel.FIELDSTATUS_OK);
					this.simParams.add(userValue);
				}
			} 
			catch (NumberFormatException exc) {
				setFieldStatusError(textField, "value should be a number");
				errorsAvailable = true;
			}
		}
		return errorsAvailable;
	}
	
	public void addSimSelectListener(final ListSelectionListener listener) {
		simList.addListSelectionListener(listener);
	}
}
